package com.rgbgame.game;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PImage;

import javax.media.opengl.GL;
import java.util.ArrayList;
import java.awt.Color;

import com.rgbgame.util.Util;
import com.rgbgame.launcher.Launcher;

public class rgbGame {


    public static final float PI = PApplet.PI;
    public static final float sin(float f) {
        return PApplet.sin(f);
    }
    public static final float cos(float f) {
        return PApplet.cos(f);
    }

    public static Launcher pApplet;
    public static rgbGame game;

    public static int width;
    public static int height;

    public static final int FRAMERATE = 30;

    public static final Color COLOR_WALL = new Color(150, 255,
    150);
    public static final Color COLOR_DEATHWALL = new Color(150, 20, 20);
    public static final Color COLOR_LIGHTGRAY = new Color(150, 150, 150);
    private static final Color COLOR_LIGHTERGRAY = new Color(200, 200, 200);


    private static final float SIN_TWOTHIRDS_PI = sin(2.0f * PI / 3.0f);
    private static final float COS_TWOTHIRDS_PI = cos(2.0f * PI / 3.0f);
    private static final float COS_FOURTHIRDS_PI = cos(4.0f * PI / 3.0f);
    private static final float SIN_FOURTHIRDS_PI = sin(4.0f * PI / 3.0f);

    public void drawRect(int x, int y, int w, int h) {

    }


    String testingLevel = "simpleclimb.rgb"; //kept at the top for easy changing
    GL gl;
    int debugCounter = 0; // the value of this variable is printed out while
    // 'd' is held down, increment it to count entrances
    // to a function (don't forget to undo it when
    // you're done!)
    String graphicsMode;
    PFont myFont;
    //public int w = 640;
    //public int h = 480;

    public static String[] levelStrings = {
    		"medium1.rgb",
            "easy1.rgb",
    		"easy2.rgb",
    		"longlevel.rgb",
            "allwalls.rgb",
            "bigsled.rgb",
            "car.rgb",
            "cleartheway.rgb",
            "crushed.rgb",
            "car.rgb",
            "debugLevel.rgb",
            "dropit.rgb",
            "fallingblock.rgb",
            "fallingblock2.rgb",
            "hamsterball.rgb",
            "level1.rgb",
            "level2.rgb",
            "level3.rgb",
            "level4.rgb",
            "simpleclimb.rgb",
            "sled.rgb",
            "sled2.rgb",
            "sled3.rgb",
            "stickywall.rgb",
            "testLevel.rgb",
            "testLevel1.rgb",
            "tower.rgb",
            "allwalls.rgb",
            "bigsled.rgb",
            "car.rgb",
            "cleartheway.rgb",
            "crushed.rgb",
            "debugLevel.rgb",
            "dropit.rgb",
            "fallingblock.rgb",
            "fallingblock2.rgb",
            "hamsterball.rgb",
            "level1.rgb",
            "level2.rgb",
            "level3.rgb",
            "level4.rgb",
            "simpleclimb.rgb",
            "sled.rgb",
            "sled2.rgb",
            "sled3.rgb",
            "stickywall.rgb",
            "testLevel.rgb",
            "testLevel1.rgb",
            "tower.rgb",
    };


    public String loadedLevelString = "";
    public boolean loadLevelsFromList = true;
    public void setup(Launcher applet, String levelString) {
        loadedLevelString = levelString;
        loadLevelsFromList = false;
        setup(applet);
    }
    public void setup(Launcher applet) {
        pApplet = applet;
        game = this;
/*
        File dir = new File(".//rgbgame//build//data");
        FilenameFilter filter = new com.rgbgame.game.rgbFilter();
        levelStrings = dir.list(filter);
        for(int i=0;i<levelStrings.length;i++) {
            System.out.println(levelStrings[i]);
        }
*/

//        pApplet.size(640, 480, PApplet.P3D);
        {
            graphicsMode = PApplet.P3D;
        }
        /*
        //size(640, 480, OPENGL);
        //graphicsMode = OPENGL;
        if (graphicsMode.equals(PApplet.OPENGL)) {
            pApplet.smooth();
            gl = ((PGraphicsOpenGL) pApplet.g).gl;
        }
        */
        pApplet.frameRate(FRAMERATE);
        myFont = pApplet.loadFont("Silom-48.vlw");
        pApplet.textFont(myFont, 48);
        pApplet.textSize(48);
        currentLevel = 0;
        myRecorder = new ActionRecorder(this);
        gameState = INTRO;
        // myBolt = new Bolt();
        levelInitialize();
        width = pApplet.width;
        height = pApplet.height;
    }

    public void levelInitialize() {

        debugVC = 0;
        if (myRecorder.dataLoaded && alreadyWonReplaying) {
            recording = false;
            playingBack = true;
            myRecorder.resetPlayer();
        } // only start replay if level has been won
        else {
            recording = true;
            playingBack = false;
            myRecorder.resetRecorder();
            alreadyWonReplaying = false;
            playerDied = false;
        } // otherwise just reset the level
        keyState[' '] = false;
        keyEnter = false;
        bgcolor = 0;
        myRecorder.dataLoaded = true;
        // recording = true;
        // playingBack = false;
        // currentLevel = 3;
        pApplet.noStroke();
        keyState['r'] = false;
        keyState['g'] = false;
        keyState['b'] = false;
        generatePoses();
        SolidWall2d.lastID = 0; // reset id counter for walls
        myPlayer = new Player(this, myParticle);
        // myPlayer.scale(.91);
        myPlayer.rotate(-PI / 2.0f);
        myPlayer.translate(new Vector3d(100, 100, 0)); // default player
        // position
        cameraX = -100 + width / 2; // to center the camera at CAMX, set cameraX
        // = -CAMX + width/2, cameraY = -CAMY +
        // height/2
        cameraY = -100 + height / 2;
        alreadyWonLevel = false;
        goalLocation = new Vector3d(550, -65, 0); // eh, default
        collidePoint = new Vector3d(10, 0, 0);
        gravityForce = new Vector3d(0.0f, gravityStrength, 0);
        // myStrut = new
        // com.rgbgame.game.Strut[4];//(myPlayer.getParticle(0),myPlayer.getParticle(1));
        myStrut = new ArrayList();
        myStaticCStruts = new ArrayList();
        myDynamicCStruts = new ArrayList();
        otherParticle = new ArrayList();
        redDStruts = new ArrayList();
        greenDStruts = new ArrayList();
        blueDStruts = new ArrayList();
        alphaDStruts = new ArrayList();
        myWall = new ArrayList();
        redGrabbing = false;
        greenGrabbing = false;
        blueGrabbing = false;
        alphaGrabbing = false;
        invertedRGB = false;
        keyLeft = false;
        keyRight = false;
        angularVelocity = 0.0f;
        smoothedAngularVelocity = 0.0f;
        maxAngularVelocity = 10000f; //default
        smoothedMouseForce = 0.0f;
        keySpinAccel = .002f;
        frameNumber = 0;
        stepCount = 0;
        // makeWalls();
        // makeStruts();
        // loadLevelLocation(getParameter("levelFile"));
        /*
           * if (levelLocation == null) { if (inputLevelString == null){ try{
           * makeLevel("http://www.gamefight.org/rgb2/defaultLevel.rgb"); } catch
           * (Exception e){ makeLevel("testLevel.rgb"); } } else{
           * makeLevelFromString(inputLevelString); } } else{
           * makeLevel(levelLocation); }
           */
        String levelToLoad = getLevelLocation(currentLevel);
        //try/catch should be reenabled for release, but for debugging
        //I want to know if there are any problems!
        //try {
        if(loadLevelsFromList) makeLevel(levelToLoad);
        else makeLevelFromString(loadedLevelString);
        //} catch (Exception e) {
        //	makeLevel(getLevelLocation(0)); // will load default level, which
        // better exist!
        //}

        fxParticles = new Particle[numFXParticles];
        for (int i = 0; i < numFXParticles; i++) {
            fxParticles[i] = new Particle(new Vector3d(ZEROVEC), new Vector3d(
                    ZEROVEC), 1.0f, 0.9f, Color.white);
            fxParticles[i].setLineDraw();
        }
        activeFXParticles = 0;

        // Might as well start things off with a burst
        for (int i = 0; i < myPlayer.particle.length; i++) {
            Particle particle = myPlayer.particle[i];
            burstFXParticles(60, particle.position, particle.position
                    .minus(particle.positionLast), particle.myColor, 1.0f,
                    455.0f);
        }

        System.out.println("Setting up spatial partition.");
        setupSpatialPartition();
        System.out.println("Level initialized, garbage collecting.");
        System.gc();
        System.out.println("Starting level play.");
    }

    public void draw() {
        if (gameState == PREINTRO)
            preIntro();
        if (gameState == INTRO)
            introFrame();
        else if (gameState == LEVEL)
            levelFrame();
        else if (gameState == PAUSE)
            pauseFrame();
    }

    public void preIntro() {
        // initialize for intro screen

        gameState = INTRO;
    }

    public void introFrame() {

        gameState = LEVEL;
    }

    public void pauseFrame() {
        if (keyState[' ']) {
            gameState = LEVEL;
            levelInitialize();
        }
    }

    boolean startWatch = false;
    String debugString;
    //com.rgbgame.game.Strut stickyStrut;

    /**
     * Main game loop, called once per frame when the level is being played.
     */
    public void levelFrame() {
        // System.out.println(frameCount);
        debugCounter = 0;
        // debugString = "";
        debugFlag = keyState['d'];
        if (keyState[' '] && alreadyWonReplaying) {
            levelInitialize();
        }
        if (keyEnter || keyState['a']) {
            myRecorder.dataLoaded = false;
            if ( /*alreadyWonReplaying &&*/ keyEnter)
                currentLevel++;
            levelInitialize();
        }

        pApplet.background(bgcolor);

        // pApplet.fill(0,0,0,120);
        // rect(-1,0,width+1,height+1);
        // handleCamera();
        pApplet.pushMatrix();
        pApplet.translate(cameraX, cameraY);
        drawStars();
        pApplet.popMatrix();

        calcAngularVelocity(); // use this for some transformations

        // do simulation loop
        for (int i = 0; i < stepsPerFrame; i++) {
            if (playingBack)
                myRecorder.loadKeyStates(stepCount);
            handleCamera();
            clickHandler(); // actually handles rotation in general, not clicks
            goalCheck(); // check if player has won level/picked up anything
            myPlayer.timeStep(tstep / stepsPerFrame);
            mySpatialPartition.refreshDynamicPartitioning();// doesn't need to
            // be done EVERY
            // time step, but
            // for now it's not
            // running too slow,
            // so...
            keyCheck(); // this is where collision checks with colored struts
            // happen - MUST BE INSIDE MULTIPLE SIMULATOR!!! [bad
            // way to design this, though...]

            // check collisions - use com.rgbgame.game.SpatialPartition to get list of walls in
            // range
            Vector3d playerCenter = new Vector3d(
                    myPlayer.getParticle(0).position);
            SolidWall2d[] collidingWalls = mySpatialPartition.getWallsInRange(
                    playerCenter.x - 100, playerCenter.y - 100,
                    playerCenter.x + 100, playerCenter.y + 100);
            for (int j = 0; j < myPlayer.numParticles; j++) {
                for (int k = 0; k < collidingWalls.length; k++) {
                    collidingWalls[k].collideTest(myPlayer.getParticle(j),
                            collidePoint, this);
                }
            }
            // if (debugFlag){ println(collidingWalls.length); }
            for (int j = 0; j < myPlayer.numParticles; j++) {
                // add gravity
                myPlayer.getParticle(j).forceAdd(
                        gravityForce.times(myPlayer.getParticle(j).mass));
            }
            for (int k = 0; k < otherParticle.size(); k++) {
                ((Particle) otherParticle.get(k)).forceAdd(gravityForce
                        .times(((Particle) otherParticle.get(k)).mass));
            }
            for (int j = 0; j < otherParticle.size(); j++) {
                ((Particle) otherParticle.get(j)).timeStep(tstep
                        / stepsPerFrame);
            }
            for (int k = 0; k < otherParticle.size(); k++) {
                // collide other particles with walls (alter velocities)
                Particle oParticle = (Particle) otherParticle.get(k);
                collidingWalls = mySpatialPartition
                        .getWallsInRange(
                                PApplet.min(oParticle.position.x,
                                        oParticle.truePositionLast.x) - 100,
                                PApplet.min(oParticle.position.y,
                                        oParticle.truePositionLast.y) - 100,
                                PApplet.max(oParticle.position.x,
                                        oParticle.truePositionLast.x) + 100,
                                PApplet.max(oParticle.position.y,
                                        oParticle.truePositionLast.y) + 100);
                for (int m = 0; m < collidingWalls.length; m++) {
                    // println("particle "+k);
                    collidingWalls[m].collideTest((Particle) otherParticle
                            .get(k), collidePoint, this);
                }
            }
            if (i == 0) {
                float currX = -12345f, currY = -12345f; //hopefully no particles here!
                //int cnt = 0;
                for (int k = 0; k < activeFXParticles; k++) {
                    // collide FX particles with walls (alter velocities)
                    Particle oParticle = fxParticles[k];// .get(k);

                    //optimized for closeness of FX particles
                    //so we don't need to keep calling getWalls...
                    //[working fairly well, though the ordering of
                    //fx particles is not ideal here, especially
                    //since the exit point spawns new ones and tends
                    //to be further away...could store those separately
                    //or not collide them, would speed this up a LOT]
                    if (PApplet.abs(oParticle.position.x - currX) > 80f ||
                            PApplet.abs(oParticle.position.y - currY) > 80f) {
                        //System.out.println(cnt++ + " "+(currX) + " " + oParticle.position.x);
                        currX = oParticle.position.x;
                        currY = oParticle.position.y;
                        collidingWalls = mySpatialPartition.getWallsInRange(
                                PApplet.min(oParticle.position.x,
                                        oParticle.truePositionLast.x) - 100, PApplet.min(
                                oParticle.position.y,
                                oParticle.truePositionLast.y) - 100, PApplet.max(
                                oParticle.position.x,
                                oParticle.truePositionLast.x) + 100, PApplet.max(
                                oParticle.position.y,
                                oParticle.truePositionLast.y) + 100);
                    }
                    for (int m = 0; m < collidingWalls.length; m++) {
                        float origRest = collidingWalls[m].restitutionCoefficient;
                        float origFric = collidingWalls[m].frictionCoefficient;
                        collidingWalls[m].restitutionCoefficient = 0.8f;
                        collidingWalls[m].frictionCoefficient = 0.2f;
                        collidingWalls[m].collideTest(fxParticles[k], collidePoint, this);
                        collidingWalls[m].restitutionCoefficient = origRest;
                        collidingWalls[m].frictionCoefficient = origFric;
                    }
                }

            }
            for (int l = 0; l < constraintIters; l++) {

                float ipx = momentumX();
                for (int j = 0; j < myStrut.size(); j++) {
                    ((Strut) myStrut.get(j)).applyConstraint(this);
                }
                for (int j = 0; j < redDStruts.size(); j++) {
                    ((Strut) redDStruts.get(j)).applyConstraint(this);
                }
                for (int j = 0; j < greenDStruts.size(); j++) {
                    ((Strut) greenDStruts.get(j)).applyConstraint(this);
                }
                for (int j = 0; j < blueDStruts.size(); j++) {
                    ((Strut) blueDStruts.get(j)).applyConstraint(this);
                }
                float opx = momentumX();
                if (PApplet.abs(opx - ipx) > 1.0f) System.out.println("Momentum gain: " + (opx - ipx));
                for (int k = 0; k < otherParticle.size(); k++) {
                    Particle oParticle = (Particle) otherParticle.get(k);
                    collidingWalls = mySpatialPartition.getWallsInRange(
                            PApplet.min(oParticle.position.x,
                                    oParticle.truePositionLast.x) - 30, PApplet.min(
                            oParticle.position.y,
                            oParticle.truePositionLast.y) - 30, PApplet.max(
                            oParticle.position.x,
                            oParticle.truePositionLast.x) + 30, PApplet.max(
                            oParticle.position.y,
                            oParticle.truePositionLast.y) + 30);

                    for (int m = 0; m < collidingWalls.length; m++) {
                        collidingWalls[m].projectCollide(
                                (Particle) otherParticle.get(k), collidePoint);
                    }
                }
                //Check solid struts against walls
                for (int j = 0; j < myStrut.size(); j++) {
                    Strut st = ((Strut) myStrut.get(j));
                    SolidStrut s;
                    if (st.colliding) s = ((SolidStrut) st);
                    else {
                        //println("skip");
                        continue;
                    }
                    //debugCounter++;
                    collidingWalls = mySpatialPartition.getWallsInRange(
                            PApplet.min(PApplet.min(s.pA.position.x, s.pA.positionLast.x), PApplet.min(s.pB.position.x, s.pB.positionLast.x)),
                            PApplet.min(PApplet.min(s.pA.position.y, s.pA.positionLast.y), PApplet.min(s.pB.position.y, s.pB.positionLast.y)),
                            PApplet.max(PApplet.max(s.pA.position.x, s.pA.positionLast.x), PApplet.max(s.pB.position.x, s.pB.positionLast.x)),
                            PApplet.max(PApplet.max(s.pA.position.y, s.pA.positionLast.y), PApplet.max(s.pB.position.y, s.pB.positionLast.y))
                    );
                    for (int k = 0; k < collidingWalls.length; k++) {
                        SolidWall2d w = collidingWalls[k];
                        Vector3d v = w.wallA;
                        s.collideTest(v, w);
                        v = w.wallB;
                        s.collideTest(v, w);
                    }
                }

                //BEGIN DEBUG - totally unoptimized
                //Check solid struts against particles

                Strut[] collidingStruts;
                for (int k = 0; k < 10; k++) {
                    Particle oParticle = myPlayer.getParticle(k);
                    float r = oParticle.radius * 2f;
                    collidingStruts = mySpatialPartition.getSolidStrutsInRange(PApplet.min(oParticle.position.x,
                            oParticle.truePositionLast.x) - r, PApplet.min(
                            oParticle.position.y,
                            oParticle.truePositionLast.y) - r, PApplet.max(
                            oParticle.position.x,
                            oParticle.truePositionLast.x) + r, PApplet.max(
                            oParticle.position.y,
                            oParticle.truePositionLast.y) + r);
                    for (int j = 0; j < collidingStruts.length; j++) {
                        if (collidingStruts[j].colliding) {
                            //float ipx = momentumX();
                            ((SolidStrut) collidingStruts[j]).collideTest(oParticle);
                            //float opx = momentumX();
                            //if (PApplet.abs(opx - ipx)>1.0f) System.out.println(opx-ipx);
                            oParticle.collideTest(((SolidStrut) collidingStruts[j]).pA, 1.0f, 0.0f);
                            oParticle.collideTest(((SolidStrut) collidingStruts[j]).pB, 1.0f, 0.0f);

                        }
                    }
                }

                for (int k = 0; k < otherParticle.size(); k++) {
                    Particle oParticle = (Particle) otherParticle.get(k);
                    float r = oParticle.radius * 2f;
                    collidingStruts = mySpatialPartition.getSolidStrutsInRange(PApplet.min(oParticle.position.x,
                            oParticle.truePositionLast.x) - r, PApplet.min(
                            oParticle.position.y,
                            oParticle.truePositionLast.y) - r, PApplet.max(
                            oParticle.position.x,
                            oParticle.truePositionLast.x) + r, PApplet.max(
                            oParticle.position.y,
                            oParticle.truePositionLast.y) + r);
                    for (int j = 0; j < collidingStruts.length; j++) {
                        if (collidingStruts[j].colliding) {
                            ((SolidStrut) collidingStruts[j]).collideTest(oParticle);

                            oParticle.collideTest(((SolidStrut) collidingStruts[j]).pA, 1.0f, 0.0f);
                            oParticle.collideTest(((SolidStrut) collidingStruts[j]).pB, 1.0f, 0.0f);
                        }
                    }
                }

                //END DEBUG
            }


            playerCenter = new Vector3d(myPlayer.getParticle(0).position);
            collidingWalls = mySpatialPartition.getWallsInRange(
                    playerCenter.x - 100, playerCenter.y - 100,
                    playerCenter.x + 100, playerCenter.y + 100);
            for (int j = 0; j < myPlayer.numParticles; j++) {
                for (int k = 0; k < collidingWalls.length; k++) {
                    collidingWalls[k].collideTest(myPlayer.getParticle(j),
                            collidePoint, this);
                }
                // for (int k=0; k<myWall.size(); k++){
                // ((com.rgbgame.game.SolidWall2d)(myWall.get(k))).collideTest(myPlayer.getParticle(j),collidePoint);
                // }
            }
            pApplet.pushMatrix();
            pApplet.translate(cameraX, cameraY);

            // draw sprites

            // draw player
            myPlayer.draw(((i + 1) / (1.0f * stepsPerFrame)) * 255);

            // draw FX particles
            if (i == 0) {
                drawFXParticles();
                stepFXParticles(tstep);
                pApplet.noStroke();
            }

            pApplet.popMatrix();

            stepCount++;
        }// end simulation loop

        pApplet.pushMatrix();
        pApplet.translate(cameraX, cameraY);
        if (!alreadyWonLevel) {
            float rnum = 255 * (0.5f + .5f * sin(frameNumber / 100.0f
                    + shootAngle));
            float gnum = 255 * (0.5f + .5f * sin(frameNumber / 100.0f + PI
                    * 2.0f / 3.0f + shootAngle));
            float bnum = 255 * (0.5f + .5f * sin(frameNumber / 100.0f + PI
                    * 4.0f / 3.0f + shootAngle));
            for (int i = 0; i < 10; i++) {
                pApplet.fill(rnum * (10 - i) / 10.0f, gnum * (10 - i) / 10.0f, bnum
                        * (10 - i) / 10.0f, 20);
                pApplet.ellipse(goalLocation.x, goalLocation.y, 5 * (10 - i),
                        5 * (10 - i));
            }
        }
        for (int j = 0; j < myStrut.size(); j++) {
            ((Strut) myStrut.get(j)).draw();
        }
        for (int j = 0; j < redDStruts.size(); j++) {
            ((Strut) redDStruts.get(j)).draw();
        }
        for (int j = 0; j < greenDStruts.size(); j++) {
            ((Strut) greenDStruts.get(j)).draw();
        }
        for (int j = 0; j < blueDStruts.size(); j++) {
            ((Strut) blueDStruts.get(j)).draw();
        }
        for (int k = 0; k < myWall.size(); k++) {
            ((SolidWall2d) myWall.get(k)).draw(this);
        }
        for (int j = 0; j < otherParticle.size(); j++) {
            ((Particle) otherParticle.get(j)).draw();
        }
        /*
           * float myLen = PApplet.abs(smoothedAngularVelocity / 5); if (myLen > 100){
           * myBolt.doStep(myPlayer.particle[1].position,myPlayer.particle[2].position,myLen);
           * myBolt.doStep(myPlayer.particle[2].position,myPlayer.particle[3].position,myLen);
           * myBolt.doStep(myPlayer.particle[3].position,myPlayer.particle[1].position,myLen); }
           */

        pApplet.noFill();
        if (debugFlag) { // debugging
            mySpatialPartition.draw();
        }
        pApplet.popMatrix();
        pApplet.noStroke();
        if (debugFlag)
            System.out.println(debugCounter);
        if (!alreadyWonLevel && !playerDied)
            frameNumber++;
        if (alreadyWonLevel)
            pApplet.fill(255, 0, 0);
        else if (playerDied)
            pApplet.fill(10, 10, 10, 0);
        else
            pApplet.fill(255);
        pApplet.text("-" + frameNumber, 10, 48);
        if (playingBack && !alreadyWonReplaying) {
            pApplet.textSize(24);
            pApplet.textAlign(PApplet.CENTER);
            pApplet.fill(200, 200, 200);
            pApplet.text("Viewing replay, press space to restart replay.", width / 2,
                    height - 34);
            pApplet.text("Press 'a' to try level again.", width / 2, height - 10);
            pApplet.textSize(48);
            pApplet.textAlign(PApplet.LEFT);
        } else if (alreadyWonReplaying) {
            pApplet.textSize(24);
            pApplet.textAlign(PApplet.CENTER);
            pApplet.fill(200, 200, 200);
            pApplet.text("You beat the level with a score of -" + winScore + "!",
                    width / 2, height - 58);
            if (!playingBack)
                pApplet.text("Press space to view replay.", width / 2, height - 34);
            else
                pApplet.text("Viewing replay, press space to restart replay.",
                        width / 2, height - 34);
            pApplet.text("Press enter for next level, 'a' to retry this one.",
                    width / 2, height - 10);
            pApplet.textSize(48);
            pApplet.textAlign(PApplet.LEFT);
        } else if (playerDied) {
            pApplet.textSize(24);
            pApplet.textAlign(PApplet.CENTER);
            pApplet.fill(200, 200, 200);
            pApplet.text("You died, press 'a' to retry level.", width / 2,
                    height - 10);
            pApplet.textSize(48);
            pApplet.textAlign(PApplet.LEFT);
        }
    }

    public float momentumX() {
        //for debugging
        float sum = 0f;
        for (int i = 0; i < myPlayer.particle.length; i++) {
            Particle p = myPlayer.getParticle(i);
            sum += p.mass * (p.position.x - p.positionLast.x);
        }
        for (int i = 0; i < otherParticle.size(); i++) {
            Particle p = (Particle) otherParticle.get(i);
            sum += p.mass * (p.position.x - p.positionLast.x);
        }
        return sum;
    }

    // Lightning bolt class
    // Bolt myBolt;
    /*
      * class Bolt{ private ArrayList segments; private float maxLength; //
      * private double strength; private float segDev; private float bias;
      * private int litColor; private int flashColor; private int segWidth = 5;
      * private Random generator;
      *
      * public Bolt(){ segments = new ArrayList(); segments.clear(); maxLength =
      * 200; generator = new Random(); segDev = 1.0; bias = .15; litColor =
      * color(1.0*255,1.0*255,1.0*255,100); flashColor =
      * color(0.2*255,0.2*255,1.0*255,0.3*255); }
      *
      * private com.rgbgame.game.Vector3d genRandomVector(float xAvg,float yAvg, float dev){ float
      * xrand = (float)(generator.nextGaussian()*dev) + xAvg; float yrand =
      * (float)(generator.nextGaussian()*dev) + yAvg; return new
      * com.rgbgame.game.Vector3d(xrand,yrand,0); }
      *
      * private boolean spawnSegments(com.rgbgame.game.Vector3d start, com.rgbgame.game.Vector3d end){
      * if(Math.abs(start.x - end.x) < maxLength){ if(Math.abs(start.y - end.y) <
      * maxLength){ if(!(start.distanceTo(end) > maxLength)){ segments.clear();
      * float currentLength = 0; segments.add(new com.rgbgame.game.Vector3d(start.x,start.y,0));
      * com.rgbgame.game.Vector3d buffer = new com.rgbgame.game.Vector3d(0,0,0); com.rgbgame.game.Vector3d lastPoint =
      * (com.rgbgame.game.Vector3d)(segments.get(0)); while( (currentLength < maxLength) &&
      * (lastPoint.distanceTo(end) > segDev) ){ buffer =
      * end.minus(lastPoint);//lastPoint.to(end); buffer.normalize();
      * buffer.multiply(bias); com.rgbgame.game.Vector3d toAdd =
      * genRandomVector(buffer.x,buffer.y,segDev); currentLength += toAdd.norm();
      * lastPoint = toAdd.plus(lastPoint); segments.add(lastPoint); } if
      * (lastPoint.distanceTo(end) < segDev) { return true; } else { return
      * false; } } else { return false; } } } return false; }
      *
      * public void doStep(com.rgbgame.game.Vector3d start, com.rgbgame.game.Vector3d end){ if
      * (spawnSegments(start,end)){ drawMe(); } }
      *
      * public void doStep(com.rgbgame.game.Vector3d start, com.rgbgame.game.Vector3d end, float maxL){ float
      * oldLength = maxLength; maxLength = maxL; if (spawnSegments(start,end)){
      * drawMe(); } maxLength = oldLength; //reset length...maybe pointless... }
      *
      * public void drawMe(){
      *  // g.setColor(flashColor); // g.setStroke(new BasicStroke(segWidth));
      * pApplet.stroke(flashColor); strokeWeight(segWidth); for(int i=0; i<(segments.size()-1) ;
      * i++){ com.rgbgame.game.Vector3d buff1 = (com.rgbgame.game.Vector3d)(segments.get(i)); com.rgbgame.game.Vector3d buff2 =
      * (com.rgbgame.game.Vector3d)(segments.get(i+1)); line(buff1.x,buff1.y,buff2.x,buff2.y); }
      *  // g.setColor(litColor); // g.setStroke(new BasicStroke(1));
      * pApplet.stroke(litColor); strokeWeight(1); for(int i=0; i<(segments.size()-1) ;
      * i++){ com.rgbgame.game.Vector3d buff1 = (com.rgbgame.game.Vector3d)(segments.get(i)); com.rgbgame.game.Vector3d buff2 =
      * (com.rgbgame.game.Vector3d)(segments.get(i+1)); line(buff1.x,buff1.y,buff2.x,buff2.y); } } }
      */

    // Recorder vars
    boolean recording = true;
    boolean playingBack = false;

    // String[] debugValue = new String[50000];
    int debugVC = 0;

    public void mousePressed() {
        if (gameState == LEVEL) {
            keyState[' '] = true;
        }
    }

    public void mouseReleased() {
        if (gameState == LEVEL) {
            keyState[' '] = false;
        }
    }

    public void keyPressed(int keyCode, int key) {
        // println(stepCount);
        if (key == ';') {
            System.out.println("switching to editor!");
            pApplet.launch(Launcher.EDITOR);
            return;
        }
        
        if ((key == ' ')) {
            keyState[' '] = true;
        }
        if (key == PApplet.ENTER || key == PApplet.RETURN) {
            keyEnter = true;
        }
        if ((key == 'a' || key == 'A')) {
            keyState['a'] = true;
        }

        if (playingBack)
            return;
        if (recording)
            myRecorder.recordDownActions(keyCode, key, stepCount);
        if (key == PApplet.CODED) {
            if (keyCode == PApplet.RIGHT) {
                keyRight = true;
            } else if (keyCode == PApplet.LEFT) {
                keyLeft = true;
            }
        }

        if ((key == 'r' || key == 'R')) {
            if (!keyState['r']) {
                rePose = true;
            }
            keyState['r'] = true;
        }

        if ((key == 'g' || key == 'G')) {
            if (!keyState['g']) {
                rePose = true;
            }
            keyState['g'] = true;
        }

        if ((key == 'b' || key == 'B')) {
            if (!keyState['b']) {
                rePose = true;
            }
            keyState['b'] = true;
        }

        if ((key == 'd' || key == 'D')) {
            keyState['d'] = true;
        }

    }

    public void keyReleased(int keyCode, int key) {
        if ((key == ' ')) {
            keyState[' '] = false;
        }
        if (key == PApplet.ENTER || key == PApplet.RETURN) {
            keyEnter = false;
        }
        if ((key == 'a' || key == 'A')) {
            keyState['a'] = false;
        }
        // Any keys that should be checked during playback should be checked
        // above (we block most input during playback)
        if (playingBack)
            return;

        if (recording)
            myRecorder.recordUpActions(keyCode, key, stepCount);
        if (key == PApplet.CODED) {
            if (keyCode == PApplet.RIGHT) {
                keyRight = false;
            } else if (keyCode == PApplet.LEFT) {
                keyLeft = false;
            }
        }

        if ((key == 'r' || key == 'R')) {
            if (keyState['r']) {
                rePose = true;
            }
            keyState['r'] = false;
        }

        if ((key == 'g' || key == 'G')) {
            if (keyState['g']) {
                rePose = true;
            }
            keyState['g'] = false;
        }
        if ((key == 'b' || key == 'B')) {
            if (keyState['b']) {
                rePose = true;
            }
            keyState['b'] = false;
        }
        if ((key == 'd' || key == 'D')) {
            keyState['d'] = false;
        }

    }

    // OPT: all of these checks ignore binning (which, if I recall, isn't set up
    // yet for colored struts) - this is a real performance drain [FIXED]
    public void keyCheck() {
        // boolean r = keyState['r'];
        boolean r = keyState['r'] | keyState[' '];
        boolean g = keyState['g'] | keyState[' '];
        boolean b = keyState['b'] | keyState[' '];
        if (rePose) {
            /*
                * // if (playingBack)
                * println(""+myPlayer.particle[2].position.x+keyState['r']+keyState['g']+keyState['b']+keyLeft+keyRight);
                * if (recording) debugValue[debugVC] =
                * ""+myPlayer.particle[2].position.x+keyState['r']+keyState['g']+keyState['b']+keyLeft+keyRight+debugString;
                * if ( playingBack &&
                * !(debugValue[debugVC].equals(""+myPlayer.particle[2].position.x+keyState['r']+keyState['g']+keyState['b']+keyLeft+keyRight+debugString))) {
                * println("Problem, other one equaled ");
                * println(debugValue[debugVC]+" at step "+stepCount+", but this one
                * is"); println(
                * ""+myPlayer.particle[2].position.x+keyState['r']+keyState['g']+keyState['b']+keyLeft+keyRight+debugString); //
                * println("Last recorded events: "+rDownEvents[rDownPlay-1]+"
                * "+rUpEvents[rUpPlay-1]+" "+gDownEvents[gDownPlay-1]+"
                * "+gUpEvents[gUpPlay-1]+" "+bDownEvents[bDownPlay-1]+"
                * "+bUpEvents[bUpPlay-1]); //println("Next recorded events:
                * "+rDownEvents[rDownPlay]+" "+rUpEvents[rUpPlay]+"
                * "+gDownEvents[gDownPlay]+" "+gUpEvents[gUpPlay]+"
                * "+bDownEvents[bDownPlay]+" "+bUpEvents[bUpPlay]); //print("Red:
                * ");print(rDownEvents); //print("Green: ");print(gDownEvents);
                * //print("Blue: ");print(bDownEvents); //exit(); gameState =
                * PAUSE; debugFlag = true; bgcolor = color(100,100,100); }
                * debugVC++;
                */
            resetPose(r, g, b);
        }

        Vector3d playerCenter = new Vector3d(myPlayer.getParticle(0).position);
        // com.rgbgame.game.SolidWall2d[] collidingWalls =
        // mySpatialPartition.getWallsInRange(playerCenter.x-100,playerCenter.y-100,playerCenter.x+100,playerCenter.y+100);
        Strut[] collidingStruts = mySpatialPartition.getStrutsInRange(
                playerCenter.x - 100, playerCenter.y - 100,
                playerCenter.x + 100, playerCenter.y + 100);

        int rNum = 0;
        int gNum = 0;
        int bNum = 0;
        for (int m = 0; m < collidingStruts.length; m++) {
            int mySCol = collidingStruts[m].getStrutColor();
            if (mySCol == REDINDEX) {
                rNum++;
            } else if (mySCol == GREENINDEX) {
                gNum++;
            } else if (mySCol == BLUEINDEX) {
                bNum++;
            }
            // else if (mySCol == 0) {aNum++;}
        }
        // println(rNum+"; "+gNum+"; "+bNum);

        Strut[] aredStruts = new Strut[rNum];
        Strut[] agreenStruts = new Strut[gNum];
        Strut[] ablueStruts = new Strut[bNum];
        // com.rgbgame.game.Strut[] aalphaStruts = new com.rgbgame.game.Strut[aNum];

        rNum = 0;
        gNum = 0;
        bNum = 0; // aNum = 0;

        for (int m = 0; m < collidingStruts.length; m++) {
            int mySCol = collidingStruts[m].getStrutColor();
            if (mySCol == REDINDEX) {
                aredStruts[rNum] = collidingStruts[m];
                rNum++;
            } else if (mySCol == GREENINDEX) {
                agreenStruts[gNum] = collidingStruts[m];
                gNum++;
            } else if (mySCol == BLUEINDEX) {
                ablueStruts[bNum] = collidingStruts[m];
                bNum++;
            }
            // else if (mySCol == 0) {aalphaStruts[aNum] = collidingStruts[m];
            // aNum++}
        }

        /*
           * //Handle alpha (collide with all) struts int aStruts =
           * alphaDStruts.size(); for (int i=0; i<aalphaStruts.length; i++){
           * handleStrutAndParticle(aalphaStruts[i].pA,aalphaStruts[i].pB,myPlayer.getParticle(REDINDEX),aalphaStruts[i]);
           * handleStrutAndParticle(aalphaStruts[i].pA,aalphaStruts[i].pB,myPlayer.getParticle(GREENINDEX),aalphaStruts[i]);
           * handleStrutAndParticle(aalphaStruts[i].pA,aalphaStruts[i].pB,myPlayer.getParticle(BLUEINDEX),aalphaStruts[i]); }
           */
        // if (keyState['r']!=r) println("Key doesn't match");
        if (r) {
            if (!redGrabbing) {
                int numStruts = redDStruts.size();
                for (int i = 0; i < aredStruts.length; i++) {
                    handleStrutAndParticle(aredStruts[i].pA, aredStruts[i].pB,
                            myPlayer.getParticle(REDINDEX), aredStruts[i]);
                }
                if (redDStruts.size() > numStruts) {
                    redGrabbing = true;
                }
            }
        } else {
            redDStruts.clear();
            redGrabbing = false;
            if (rnodrawStrut != null) {
                rnodrawStrut.setDraw(true);
                rnodrawStrut.setApply(true);
            }
            rnodrawStrut = null;
        }

        if (g) {
            if (!greenGrabbing) {
                int numStruts = greenDStruts.size();
                for (int i = 0; i < agreenStruts.length; i++) {
                    handleStrutAndParticle(agreenStruts[i].pA,
                            agreenStruts[i].pB, myPlayer
                            .getParticle(GREENINDEX), agreenStruts[i]);
                }
                if (greenDStruts.size() > numStruts) {
                    greenGrabbing = true;
                }
            }
        } else {
            greenDStruts.clear();
            greenGrabbing = false;
            if (gnodrawStrut != null) {
                gnodrawStrut.setDraw(true);
                gnodrawStrut.setApply(true);
            }
            gnodrawStrut = null;
        }
        
        if (b) {
            if (!blueGrabbing) {
                int numStruts = blueDStruts.size();
                for (int i = 0; i < ablueStruts.length; i++) {
                    handleStrutAndParticle(ablueStruts[i].pA,
                            ablueStruts[i].pB, myPlayer.getParticle(BLUEINDEX),
                            ablueStruts[i]);
                }
                if (blueDStruts.size() > numStruts) {
                    blueGrabbing = true;
                }
            }
        } else {
            blueDStruts.clear();
            blueGrabbing = false;
            if (bnodrawStrut != null) {
                bnodrawStrut.setDraw(true);
                bnodrawStrut.setApply(true);
            }
            bnodrawStrut = null;
        }
        // rePose = false;
    }

    public void checkInversion() {
        Vector3d v1 = myPlayer.getParticle(REDINDEX).position
                .directionTo(myPlayer.getParticle(GREENINDEX).position);
        Vector3d v2 = myPlayer.getParticle(REDINDEX).position
                .directionTo(myPlayer.getParticle(BLUEINDEX).position);
        float cross = v1.cross(v2).z;
        if (cross > 0) {
            invertedRGB = false;
        } else {
            invertedRGB = true;
        }
    }

    public void clickHandler() {
        // OPT: there's a lot of temporary object creation here, this could be a
        // major drain on the processor
        checkInversion();
        // com.rgbgame.game.Vector3d mouseVec = new com.rgbgame.game.Vector3d(mouseX-cameraX, mouseY-cameraY, 0);
        // com.rgbgame.game.Vector3d displacement =
        // mouseVec.minus(myPlayer.getParticle(0).position);
        // mouseVec.set(pmouseX-cameraX,pmouseY-cameraY, 0);
        // com.rgbgame.game.Vector3d oldDisplacement =
        // mouseVec.minus(myPlayer.getParticle(0).position);
        // com.rgbgame.game.Vector3d change = displacement.minus(oldDisplacement);
        float spinStrength = 0;// -mouseSpinStrength*displacement.cross(change).z*.001;
        if (keyLeft) {
            spinStrength = maxAbsSpin * keySpinFactor;
            keySpinFactor = 1.0f * keySpinAccel + keySpinFactor
                    * (1.0f - keySpinAccel);
        } else if (keyRight) {
            spinStrength = -maxAbsSpin * keySpinFactor;
            keySpinFactor = 1.0f * keySpinAccel + keySpinFactor
                    * (1.0f - keySpinAccel);
        } else
            keySpinFactor = origKeySpinFactor;

        if (PApplet.abs(spinStrength) > maxAbsSpin) {
            spinStrength *= maxAbsSpin / PApplet.abs(spinStrength);
        }
        mouseForce = spinStrength;
        smoothedMouseForce = smoothedMouseForce * .93f + mouseForce * .003f;
        if (invertedRGB) {
            spinStrength *= -1.0f;
        }
        float spinSign = (spinStrength > 0.0f) ? 2 * stepsPerFrame : -2
                * stepsPerFrame;
        if (angularVelocity > maxAngularVelocity && spinSign > 0f) return;
        else if (angularVelocity < -maxAngularVelocity && spinSign < 0f) return;
        if (spinStrength == 0f) return;
        if (angularVelocity * spinSign < 0f) spinStrength *= brakingFactor;
        // com.rgbgame.game.Vector3d currForce =
        // myPlayer.getParticle(REDINDEX).position.directionTo(myPlayer.getParticle(BLUEINDEX).position).times(spinStrength);
        float myA = 255.0f * PApplet.abs(spinStrength / maxAbsSpin) + 5;
        Vector3d currForce = myPlayer.getParticle(GREENINDEX).position.minus(
                myPlayer.getParticle(BLUEINDEX).position).normalized().times(
                -spinStrength);
        myPlayer.addForce(currForce, REDINDEX);
        if (spinBurst)
            burstFXParticles(1, myPlayer.getParticle(REDINDEX).position,
                    currForce.times(-spinSign / spinStrength), myPlayer
                    .getParticle(REDINDEX).myColor, 1.9f, myA);
        // currForce =
        // myPlayer.getParticle(GREENINDEX).position.directionTo(myPlayer.getParticle(REDINDEX).position).times(spinStrength);
        currForce = myPlayer.getParticle(BLUEINDEX).position.minus(
                myPlayer.getParticle(REDINDEX).position).normalized().times(
                -spinStrength);
        myPlayer.addForce(currForce, GREENINDEX);
        if (spinBurst)
            burstFXParticles(1, myPlayer.getParticle(GREENINDEX).position,
                    currForce.times(-spinSign / spinStrength), myPlayer
                    .getParticle(GREENINDEX).myColor, 1.9f, myA);
        // currForce =
        // myPlayer.getParticle(BLUEINDEX).position.directionTo(myPlayer.getParticle(GREENINDEX).position).times(spinStrength);
        currForce = myPlayer.getParticle(REDINDEX).position.minus(
                myPlayer.getParticle(GREENINDEX).position).normalized().times(
                -spinStrength);
        myPlayer.addForce(currForce, BLUEINDEX);
        if (spinBurst)
            burstFXParticles(1, myPlayer.getParticle(BLUEINDEX).position,
                    currForce.times(-spinSign / spinStrength), myPlayer
                    .getParticle(BLUEINDEX).myColor, 1.9f, myA);
        // particleShot(myPlayer.getParticle(REDINDEX).position,

        // burstFXParticles(,myPlayer.getParticle(REDINDEX).position,myPlayer.getParticle(REDINDEX).position.minus(myPlayer.getParticle(REDINDEX).positionLast),myPlayer.getParticle(REDINDEX).myColor,1.0);

    }

    // All global variables should eventually end up here so we know where to
    // look for them.
    // We should also organize this file a bit better than it currently is so
    // they are easier to find.

    boolean alreadyWonReplaying = false; // true if level has been won, and
    // replay is happening

    int winScore = 0;

    // Game state constants
    public static final int PREINTRO = -1;
    public static final int INTRO = 0;
    public static final int LEVEL = 1;
    public static final int PAUSE = 2;

    boolean playerDied = false;

    int gameState = PREINTRO; // init to preintro screen

    ActionRecorder myRecorder;

    int stepCount = 0; // count of steps, including multiple simulated steps
    // (used mainly for recording)

    Vector3d bufferA = new Vector3d(0, 0, 0); // reserved for com.rgbgame.game.Particle class
    Vector3d bufferB = new Vector3d(0, 0, 0); // reserved for com.rgbgame.game.Particle class
    //BufferC and D are now final static Vector3ds created in the com.rgbgame.game.Vector3d class



    int bgcolor = 0;

    Vector3d goalLocation;

    float shootAngle = 0.0f; // controls angle that particles spew out of
    // goal marker at

    static final boolean drawing3d = false;
    final boolean sphere3d = false;
    boolean alreadyWonLevel;

    final float defaultRadius = 5.0f;
    final float defaultDelta = 1.0f;
    final Color defaultColor = Color.white;

    String inputLevelString;
    String levelString = "";

    float MAX_SPRING_CONSTANT = 15000.0f;
    // final com.rgbgame.game.Vector3d zero = new com.rgbgame.game.Vector3d(0,0,-480);
    // final com.rgbgame.game.Vector3d maxV = new com.rgbgame.game.Vector3d(640,480,0);
    final int stepsPerFrame = 5;
    float curlThresh = 20.0f;
    float curlSens = 0.05f;
    int currentLevel = 1;
    String levelLocation = "level1.rgb";
    float tstep = 0.015f * (24f/FRAMERATE);
    int constraintIters = 2;
    float myMass = 1.0f;
    float myColorMass = 1.4f * myMass;
    float myRadius = 6.0f;
    float myColorRadius = myRadius * 1.2f;
    float myLegLength = 30.0f;
    float legExtendFactor = 1.8f;
    float gravityStrength = 1700.0f;
    float mouseSpinStrength = 2000;
    float maxAbsSpin = 12000;
    final float brakingFactor = 3.0f;
    
    float camFollowSpeed = .05f;
    float myXMultiplier = 1.0f; // camera follow speed stuff
    float myYMultiplier = 1.0f;
    boolean debugFlag = false;

    public static final int REDINDEX = 1;
    public static final int GREENINDEX = 2;
    public static final int BLUEINDEX = 3;

    boolean redGrabbing = false;
    boolean greenGrabbing = false;
    boolean blueGrabbing = false;
    boolean alphaGrabbing = false;
    float mouseForce = 0.0f;
    boolean invertedRGB = false; // flag set if RGB pops inside out -
    // normally ordered clockwise RGB; if
    // counterclockwise, this should be flagged
    float angularVelocity = 0.0f;
    float smoothedAngularVelocity = 0.0f;
    float maxAngularVelocity = 10000.0f;
    float smoothedMouseForce = 0.0f;
    float origKeySpinFactor = .5f;
    float keySpinFactor = origKeySpinFactor;
    float keySpinAccel = .002f;

    boolean spinBurst = false; //controls whether spin forces cause
    // particle ejection

    public static final Vector3d ZEROVEC = new Vector3d(0, 0, 0);
/*
    final com.rgbgame.game.Vector3d WHITEVEC = new com.rgbgame.game.Vector3d(255, 255, 255);
    final com.rgbgame.game.Vector3d REDVEC = new com.rgbgame.game.Vector3d(255, 0, 0);
    final com.rgbgame.game.Vector3d GREENVEC = new com.rgbgame.game.Vector3d(0, 255, 0);
    final com.rgbgame.game.Vector3d BLUEVEC = new com.rgbgame.game.Vector3d(0, 0, 255);
*/
    boolean keyState[] = new boolean[256];
    boolean keyLeft, keyRight, keyEnter;
    boolean rePose = true;

    Particle myParticle[] = new Particle[10];
    Player myPlayer;
    Pose myPose;
    // com.rgbgame.game.Particle worldParticles[];
    // com.rgbgame.game.SolidWall2d myWall[];
    ArrayList myWall; // List of all walls
    ArrayList otherParticle; // List of non-player particles that _do_
    // collide with scenery
    ArrayList otherParticleNoCollide; // currently not used...implement this,
    // though, it will be useful

    Strut[] redStruts;
    Strut[] greenStruts;
    Strut[] blueStruts; // Lists of red, blue, and green struts (only used for
    // collision detection, not constraint application)
    Strut[] alphaStruts;
    ArrayList myStrut; // List of all non-dynamical struts - better to copy
    // this into a flat array and access from there!
    ArrayList redDStruts;
    ArrayList greenDStruts;
    ArrayList blueDStruts; // dynamical struts (i.e. the ones that are used to
    // hold the player to a rope and thus
    // appear/disappear)
    ArrayList alphaDStruts;
    ArrayList myStaticCStruts;// All non-moving colliding struts (pinned
    // particle on both ends)
    ArrayList myDynamicCStruts;// Moving colliding struts

    ArrayList allParticles; // Low level complete lists of particles/struts to
    // look up by ID number
    ArrayList allStruts;

    // If we're grabbing a strut, we don't draw or apply the original strut, we
    // draw the resulting ones - the original is stored away [MESSY MESSY BAD
    // ERIC]
    Strut rnodrawStrut;
    Strut gnodrawStrut;
    Strut bnodrawStrut;

    int numWalls;
    Vector3d collidePoint;
    Vector3d gravityForce;
    public float cameraX, cameraY, cameraZ;
    SpatialPartition mySpatialPartition;

    // PImage noiseTile;
    PImage testImage;
    Sprite testSprite;

    int frameNumber;

    Particle[] fxParticles;
    // com.rgbgame.game.Particle bufferFXParticle;
    int numFXParticles = 2000;
    int activeFXParticles = 0;

    /*
      *
      *
      * //For making ropes: for (int i=0; i<myNum; i++){ com.rgbgame.game.Vector3d pos = new
      * com.rgbgame.game.Vector3d((i/(myNum-1.0))*300+myPlayer.getParticle(GREENINDEX).position.x,myPlayer.getParticle(GREENINDEX).position.y+20.0*(i/(myNum-1.0)),0);
      * myParts[i] = new com.rgbgame.game.Particle(pos,new com.rgbgame.game.Vector3d(pos),10.1/myNum,2.0,new
      * com.rgbgame.game.Vector3d(100,199,100)); if (i==myNum-1||i==1){ myParts[i].setPinned();}
      * otherParticle.add(myParts[i]); buffer.add(myParts[i]); }
      * strutList(buffer,myStrut); greenStruts = new com.rgbgame.game.Strut[buffer.size()-1]; for
      * (int i=0; i<buffer.size()-1; i++){ greenStruts[i] =
      * (com.rgbgame.game.Strut)myStrut.get(myStrut.size()-buffer.size()+1+i); }
      *
      * buffer.clear(); for (int i=0; i<myNum; i++){ com.rgbgame.game.Vector3d pos = new
      * com.rgbgame.game.Vector3d((i/(myNum-1.0))*400+myPlayer.getParticle(REDINDEX).position.x,myPlayer.getParticle(REDINDEX).position.y-40.0*(i/(myNum-1.0)),0);
      * myParts[i] = new com.rgbgame.game.Particle(pos,new com.rgbgame.game.Vector3d(pos),10.1/myNum,2.0,new
      * com.rgbgame.game.Vector3d(199,100,100)); if (i==myNum-1||i==0){ myParts[i].setPinned();}
      * otherParticle.add(myParts[i]); buffer.add(myParts[i]); }
      * strutList(buffer,myStrut); redStruts = new com.rgbgame.game.Strut[buffer.size()-1]; for
      * (int i=0; i<buffer.size()-1; i++){ redStruts[i] =
      * (com.rgbgame.game.Strut)myStrut.get(myStrut.size()-buffer.size()+1+i); } buffer.clear();
      * for (int i=0; i<myNum; i++){ com.rgbgame.game.Vector3d pos = new
      * com.rgbgame.game.Vector3d((i/(myNum-1.0))*100+myPlayer.getParticle(BLUEINDEX).position.x,myPlayer.getParticle(BLUEINDEX).position.y+30.0*(i/(myNum-1.0)),0);
      * myParts[i] = new com.rgbgame.game.Particle(pos,new com.rgbgame.game.Vector3d(pos),10.1/myNum,2.0,new
      * com.rgbgame.game.Vector3d(100,100,199)); if (i==myNum-10){ myParts[i].setPinned();}
      * otherParticle.add(myParts[i]); buffer.add(myParts[i]); }
      * strutList(buffer,myStrut); blueStruts = new com.rgbgame.game.Strut[buffer.size()-1]; for
      * (int i=0; i<buffer.size()-1; i++){ blueStruts[i] =
      * (com.rgbgame.game.Strut)myStrut.get(myStrut.size()-buffer.size()+1+i); } //End making
      * ropes
      *
      */

    /*
      * This tab is a mess, it has a whole bunch of utility functions involved in
      * level creation, but many of them are not even used anymore, some are
      * empty, and some are just real messy programming. This should be cleaned
      * up at some point, but for the moment, it's working, so I'll leave it. -E
      */

    public String getLevelLocation(int levelNumber) {
        if (levelNumber < levelStrings.length)
            return levelStrings[levelNumber];
        else return testingLevel;

        //return "sled.rgb";
        /*
          if (levelNumber == 1) {
              return "level1.rgb";
              //return "simpleclimb.rgb";
              //return "level4.rgb";
              //eturn "allwalls.rgb";
          }
          if (levelNumber == 2) {
              return "level2.rgb";
          }
          if (levelNumber == 3) {
              return "level3.rgb";
          } else {
              println("Level number " + levelNumber
                      + " does not exist.  Loading default level.");
              // return "level3.rgb";
              // return "testLevel.rgb";
              return "debugLevel.rgb";
          }//*/
    }

    /*
      * Currently implemented syntax: Order: walls, particles, struts Colors:
      * RED, GREEN, BLUE, INVISIBLE
      *
      * Walls: (each wallBegin->wallEnd call creates a strip of connected walls)
      *
      * "wallBegin wallPoint,wallX,wallY wallPoint,... ... wallEnd"
      *
      * "iceWallBegin iceWallPoint... iceWallEnd"
      * "deathWallBegin..."
      * "stickyWallBegin..."
      *
      * Particles: "particlesBegin particle,partX,partY,mass,radius,color
      * pinParticle,partX,partY,mass,radius,color ... particlesEnd"
      *
      * Struts: "strutsBegin strut,firstParticleIndex,secondParticleIndex,color
      * ... strutsEnd"
      *
      */

    public void strutList(ArrayList partList, ArrayList strutArray) {
        if (partList.size() > 1) {
            for (int i = 1; i < partList.size(); i++) {
                strutArray.add(new Strut(this, (Particle) partList.get(i - 1),
                        (Particle) partList.get(i)));
            }
        }
    }

    public void wallList(Vector3d[] myPoints, ArrayList wallList) {
        if (myPoints.length > 0) {
            for (int i = 1; i < myPoints.length; i++) {
                wallList.add(new SolidWall2d(myPoints[i - 1], myPoints[i]));
            }
        }
    }

    public void iceWallList(Vector3d[] myPoints, ArrayList wallList) {
        if (myPoints.length > 0) {
            for (int i = 1; i < myPoints.length; i++) {
                wallList.add(new SolidWall2d(myPoints[i - 1], myPoints[i],
                        0.0f, 0.0f, Color.blue));
            }
        }
    }

    public void deathWallList(Vector3d[] myPoints, ArrayList wallList) {
        if (myPoints.length > 0) {
            for (int i = 1; i < myPoints.length; i++) {
                SolidWall2d deathWall = new SolidWall2d(myPoints[i - 1],
                        myPoints[i], 0.0f, 0.0f, COLOR_DEATHWALL);
                deathWall.typeFlag = SolidWall2d.DEATHWALL;
                wallList.add(deathWall);
            }
        }
    }

    public void stickyWallList(Vector3d[] myPoints, ArrayList wallList) {
        if (myPoints.length > 0) {
            for (int i = 1; i < myPoints.length; i++) {
                SolidWall2d stickyWall = new SolidWall2d(myPoints[i - 1],
                        myPoints[i], 0.0f, 0.0f, COLOR_WALL);
                stickyWall.typeFlag = SolidWall2d.STICKYWALL;
                wallList.add(stickyWall);
            }
        }
    }

    public void setLevelLocation(String ll) {
        levelLocation = ll;
    }

    public void setLevelFromString(String ls) {
        inputLevelString = ls;
    }

    public void makeStruts() {
        // irrelevant, remove after tracking down callers
    }

    public void makeWalls() {
        // irrelevant, remove after tracking down callers
    }

    public String loadLevel(String filename) {
        String[] buff;
        try {
            buff = pApplet.loadStrings(filename);
        } catch (Exception e) {
            System.out.println("File not found: "+filename);
            return "";
        }

        if (buff == null || buff.length == 0) {
        	System.out.println("File not found: "+filename);
        	return "";
        }

        String returnValue = "";
        for (int i = 0; i < buff.length; i++) {
            // bad dog, this is an idiotic way to check for // comments
            // It will break if whitespace precedes them, and it won't
            // allow you to add comments to the end of a valid line.
            // Should also add /* */ comment removal at this stage.
            if (!(buff[i].length() > 1 && buff[i].charAt(0) == '/' && buff[i]
                    .charAt(1) == '/'))
                returnValue += buff[i] + "\n";
        }
        System.out.println("Level loaded, beginning parse.");
        return returnValue;
    }

    public void makeLevel(String filename) {
        System.out.println("Loading " + filename);
        parseLevelString(loadLevel(filename));
        System.out.println(filename + " parsed");
        numWalls = myWall.size(); // myWall is list of all walls
        levelString = "";
    }

    public void makeLevelFromString(String toParse) {
        parseLevelString(toParse);
        numWalls = myWall.size();
        levelString = "";
    }

    public String currentLevelString = "";
    public String print() {
        return currentLevelString;
    }
    public void parseLevelString(String levelString) {
        currentLevelString = levelString;
        parseHeader(levelString);
        System.out.println("Header parsed");
        parseWalls(levelString);
        System.out.println("Walls parsed");
        parseParticles(levelString);
        System.out.println("Particles parsed");
        parseStruts(levelString);
        System.out.println("Struts parsed");
    }

    public void parseHeader(String levelString) {
        // Parse all the crap that doesn't need its own section, like one-off
        // variables and such.
        // This would be the place to add other user-tunable parameters.
        // For some reason the player is also initialized here...I suck.

        String[] newHeaders = levelString.split("headerBegin\n");
        if (newHeaders.length > 1) {
            newHeaders = newHeaders[1].split("headerEnd");
            String[] headerList = newHeaders[0].split("\n");
            //int numHs = headerList.length;

            myPlayer = new Player(this, myParticle);
            myPlayer.rotate(-PI / 2.0f); // otherwise player starts out
            // rotated oddly

            for (int i = 0; i < headerList.length; i++) {
                String[] currentHeader = headerList[i].split(",");
                String toSet = currentHeader[0];
                float parameter = Float.parseFloat(currentHeader[1]);
                if (toSet.equals("GRAVITY")) {
                    gravityStrength = parameter;
                    gravityForce = new Vector3d(0.0f, gravityStrength, 0);
                } else if (toSet.equals("GOALX")) {
                    goalLocation.x = parameter;
                } else if (toSet.equals("GOALY")) {
                    goalLocation.y = parameter;
                } else if (toSet.equals("PLAYERX")) {
                    myPlayer.translate(new Vector3d(parameter, 0, 0)); // default
                    // player
                    // position
                } else if (toSet.equals("PLAYERY")) {
                    myPlayer.translate(new Vector3d(0, parameter, 0));
                } else if (toSet.equals("SPRINGCONSTANT")) {
                    myPlayer.springConstant = parameter;
                } else if (toSet.equals("MAXANGULARVELOCITY")) {
                    maxAngularVelocity = parameter;
                } else if (toSet.equals("MAXABSSPIN")) {
                	maxAbsSpin = parameter;
                } else if (toSet.equals("ANGULARACCELERATION")) {
                	keySpinAccel = parameter;
                }

            }
        }
    }

    public void parseParticles(String levelString) {
        //There's a bug here if the particle list is empty...
        otherParticle = new ArrayList();
        // only one section of particle adds allowed, with no garbage inside it
        // - otherwise things get real nasty here
        String[] newParticles = levelString.split("particlesBegin\n");
        if (newParticles.length > 1) {
            //println(1);
            newParticles = newParticles[1].split("particlesEnd");
            //println(2);
            String[] particleList = newParticles[0].split("\n");
            //println(3);
            int numPs = particleList.length;
            //println(numPs);
            Particle[] parts = new Particle[numPs];
            //println(4);
            for (int i = 0; i < particleList.length; i++) {
                //println(5);
                String[] currentParticle = particleList[i].split(",");
                if (currentParticle.length < 5) continue;
                float xCoord = Float.parseFloat(currentParticle[1]);
                float yCoord = Float.parseFloat(currentParticle[2]);
                float pMass = Float.parseFloat(currentParticle[3]);
                float pRad = Float.parseFloat(currentParticle[4]);
                Color pCol;
                if (currentParticle[5].equals("RED")) {
                    pCol = Color.red;
                } else if (currentParticle[5].equals("GREEN")) {
                    pCol = Color.green;
                } else if (currentParticle[5].equals("BLUE")) {
                    pCol = Color.blue;
                } else {
                    pCol = Color.white;
                }
                parts[i] = new Particle(new Vector3d(xCoord, yCoord, 0), pMass,
                        pRad, pCol);
                if (currentParticle[0].equals("pinParticle")) {
                    parts[i].setPinned();
                }
                if (currentParticle[0].equals("scriptedParticle")) {
                    int entries = currentParticle.length - 6;
                    int points = entries / 3;
                    Vector3d[] ps = new Vector3d[points];
                    float[] ts = new float[points];
                    for (int g = 0; g < points; g++) {
                        ts[g] = Float.parseFloat(currentParticle[6 + 3 * g]);
                        ps[g] = new Vector3d(Float.parseFloat(currentParticle[6 + 3 * g + 1]),
                                Float.parseFloat(currentParticle[6 + 3 * g + 2]),
                                0);
                    }
                    parts[i].setScript(ps, ts);
                }
                parts[i].createID();
                otherParticle.add(parts[i]);
            }
        }
    }

    public void parseStruts(String levelString) {
        myStrut = new ArrayList();
        // depends on otherParticle array being already initialized and
        // processed!
        String[] newStruts = levelString.split("strutsBegin\n");
        if (newStruts.length > 1) {
            if (newStruts[1].startsWith("strutsEnd")) return;
            newStruts = newStruts[1].split("strutsEnd");
            String[] strutList = newStruts[0].split("\n");
            int numStruts = strutList.length;
            Strut[] struts = new Strut[numStruts];
            int redStrutCount = 0;
            int greenStrutCount = 0;
            int blueStrutCount = 0;
            int whiteStrutCount = 0;
            for (int i = 0; i < strutList.length; i++) {
                String[] currentStrut = strutList[i].split(",");
                //if (currentStrut.length < 4) continue;
                //println(currentStrut[1]+" "+currentStrut[2]);
                Particle partA = null;
                Particle partB = null;
                try{
                partA = (Particle) otherParticle
                        .get(Integer.parseInt(currentStrut[1]));
                partB = (Particle) otherParticle
                        .get(Integer.parseInt(currentStrut[2]));
                } catch (Exception e) {
                	System.out.println(strutList[i]);
                	System.exit(0);
                }
                Color pCol;
                boolean colored = false;
                boolean drawn = true;
                boolean colliding = false;
                if (currentStrut[3].equals("RED")) {
                    pCol = Color.red;
                    redStrutCount++;
                    colored = true;
                } else if (currentStrut[3].equals("GREEN")) {
                    pCol = Color.green;
                    greenStrutCount++;
                    colored = true;
                } else if (currentStrut[3].equals("BLUE")) {
                    pCol = Color.blue;
                    blueStrutCount++;
                    colored = true;
                } else if (currentStrut[3].equals("INVISIBLE")) {
                    pCol = COLOR_LIGHTGRAY;
                    drawn = false;
                    colored = false;
                } else if (currentStrut[3].equals("COLLIDING")) {
                    pCol = COLOR_LIGHTERGRAY;
                    colored = false;
                    colliding = true;
                } else {
                    pCol = Color.white;
                    whiteStrutCount++;
                }


                if (colliding) struts[i] = new SolidStrut(this, partA, partB);
                else struts[i] = new Strut(this, partA, partB);
                //struts[i].distance = struts[i].distance*.5f;
                //struts[i].distanceSqr = struts[i].distance*struts[i].distance;
                struts[i].strutColor = pCol;
                struts[i].createID();
                struts[i].setDraw(drawn);
                struts[i].colliding = colliding;
                myStrut.add(struts[i]);
                if (colored && struts[i].pA.pinned && struts[i].pB.pinned) {
                    myStaticCStruts.add(struts[i]);
                } // if both edges are pinned, add to static colliding list -
                // change this bit if we allow collisions with uncolored
                // struts!!!
                else if (colored || colliding) {
                    myDynamicCStruts.add(struts[i]);
                }

            }
            redStruts = new Strut[redStrutCount];
            greenStruts = new Strut[greenStrutCount];
            blueStruts = new Strut[blueStrutCount];
            int redIndex = 0;
            int greenIndex = 0;
            int blueIndex = 0;
            for (int i = 0; i < strutList.length; i++) {
                String[] currentStrut = strutList[i].split(",");
                if (currentStrut[3].equals("RED")) {
                    redStruts[redIndex] = struts[i];
                    redIndex++;
                } else if (currentStrut[3].equals("GREEN")) {
                    greenStruts[greenIndex] = struts[i];
                    greenIndex++;
                } else if (currentStrut[3].equals("BLUE")) {
                    blueStruts[blueIndex] = struts[i];
                    blueIndex++;
                }
            }
        } else {
            redStruts = new Strut[0];
            greenStruts = new Strut[0];
            blueStruts = new Strut[0];
        }

    }

    public void parseWalls(String levelString) {
        {
            String[] wallPaths = levelString.split("wallBegin\n");
            for (int j = 0; j < wallPaths.length; j++) {
                String[] commandList = wallPaths[j].split("\n");
                // println(levelString);
                int numWallPts = 0;
                for (int i = 0; i < commandList.length; i++) {
                    if (commandList[i].split(",")[0].equals("wallPoint")) {
                        numWallPts++;
                    }
                    if (commandList[i].split(",")[0].equals("wallEnd")) {
                        i = commandList.length;
                    }
                }

                Vector3d[] wallVecs = new Vector3d[numWallPts];
                int wallIndex = 0;
                for (int i = 0; i < commandList.length; i++) {
                    String[] currentCommand = commandList[i].split(",");

                    // println(currentCommand[0]);
                    if (currentCommand[0].equals("wallPoint")) {

                        float xCoord = Float.parseFloat(currentCommand[1]);
                        float yCoord = Float.parseFloat(currentCommand[2]);
                        wallVecs[wallIndex] = new Vector3d(xCoord, yCoord, 0);
                        wallIndex++;
                    }
                    if (currentCommand[0].equals("wallEnd")) {
                        i = commandList.length;
                    }
                }
                wallList(wallVecs, myWall);
            }
        }

        {// iceWall segment
            String[] wallPaths = levelString.split("iceWallBegin\n");
            for (int j = 0; j < wallPaths.length; j++) {
                String[] commandList = wallPaths[j].split("\n");
                // println(levelString);
                int numWallPts = 0;
                for (int i = 0; i < commandList.length; i++) {
                    if (commandList[i].split(",")[0].equals("iceWallPoint")) {
                        numWallPts++;
                    }
                    if (commandList[i].split(",")[0].equals("iceWallEnd")) {
                        i = commandList.length;
                    }
                }

                Vector3d[] wallVecs = new Vector3d[numWallPts];
                int wallIndex = 0;
                for (int i = 0; i < commandList.length; i++) {
                    String[] currentCommand = commandList[i].split(",");
                    // println(currentCommand[0]);
                    if (currentCommand[0].equals("iceWallPoint")) {
                        float xCoord = Float.parseFloat(currentCommand[1]);
                        float yCoord = Float.parseFloat(currentCommand[2]);
                        wallVecs[wallIndex] = new Vector3d(xCoord, yCoord, 0);
                        wallIndex++;
                    }
                    if (currentCommand[0].equals("iceWallEnd")) {
                        i = commandList.length;
                    }
                }
                iceWallList(wallVecs, myWall);
            }
        }

        {// deathWall segment
            String[] wallPaths = levelString.split("deathWallBegin\n");
            for (int j = 0; j < wallPaths.length; j++) {
                String[] commandList = wallPaths[j].split("\n");
                // println(levelString);
                int numWallPts = 0;
                for (int i = 0; i < commandList.length; i++) {
                    if (commandList[i].split(",")[0].equals("deathWallPoint")) {
                        numWallPts++;
                    }
                    if (commandList[i].split(",")[0].equals("deathWallEnd")) {
                        i = commandList.length;
                    }
                }

                Vector3d[] wallVecs = new Vector3d[numWallPts];
                int wallIndex = 0;
                for (int i = 0; i < commandList.length; i++) {
                    String[] currentCommand = commandList[i].split(",");
                    // println(currentCommand[0]);
                    if (currentCommand[0].equals("deathWallPoint")) {
                        float xCoord = Float.parseFloat(currentCommand[1]);
                        float yCoord = Float.parseFloat(currentCommand[2]);
                        wallVecs[wallIndex] = new Vector3d(xCoord, yCoord, 0);
                        wallIndex++;
                    }
                    if (currentCommand[0].equals("deathWallEnd")) {
                        i = commandList.length;
                    }
                }
                deathWallList(wallVecs, myWall);
            }
        }
        {// stickyWall segment
            String[] wallPaths = levelString.split("stickyWallBegin\n");
            for (int j = 0; j < wallPaths.length; j++) {
                String[] commandList = wallPaths[j].split("\n");
                // println(levelString);
                int numWallPts = 0;
                for (int i = 0; i < commandList.length; i++) {
                    if (commandList[i].split(",")[0].equals("stickyWallPoint")) {
                        numWallPts++;
                    }
                    if (commandList[i].split(",")[0].equals("stickyWallEnd")) {
                        i = commandList.length;
                    }
                }

                Vector3d[] wallVecs = new Vector3d[numWallPts];
                int wallIndex = 0;
                for (int i = 0; i < commandList.length; i++) {
                    String[] currentCommand = commandList[i].split(",");
                    // println(currentCommand[0]);
                    if (currentCommand[0].equals("stickyWallPoint")) {
                        float xCoord = Float.parseFloat(currentCommand[1]);
                        float yCoord = Float.parseFloat(currentCommand[2]);
                        wallVecs[wallIndex] = new Vector3d(xCoord, yCoord, 0);
                        wallIndex++;
                    }
                    if (currentCommand[0].equals("stickyWallEnd")) {
                        i = commandList.length;
                    }
                }
                stickyWallList(wallVecs, myWall);
            }
        }
    }

    /*
      * void iceWallList(com.rgbgame.game.Vector3d[] myPoints, ArrayList wallList){ if
      * (myPoints.length > 0){ for (int i=1; i<myPoints.length; i++){
      * wallList.add(new com.rgbgame.game.SolidWall2d(myPoints[i-1],myPoints[i],0.0,0.0,new
      * com.rgbgame.game.Vector3d(0.0,0.0,255.0))); } } }
      */

    /**
     * Game logic stuff Should be pretty sparse, because there's not much logic
     * to this game...
     */

    public void playerDied() {
        // nothing yet, need to fill in
    }

    public void playerWonLevel() {
        myPlayer.drawn = false;
        myPlayer.simulating = false;
        for (int i = 0; i < myPlayer.particle.length; i++) {
            Particle particle = myPlayer.particle[i];
            burstFXParticles(100, particle.position, particle.position
                    .minus(particle.positionLast), particle.myColor, 1.1f,
                    255.0f);
        }
        alreadyWonLevel = true;
        recording = false;
        playingBack = false;
        alreadyWonReplaying = true;
        winScore = frameNumber;
        System.out.println("You beat the level in " + winScore + " frames!");
        // currentLevel++;
    }

    public void goalCheck() {
        // also draws goal

        if (!alreadyWonLevel) {
            // add some FX particles to show the goal

            Vector3d position = goalLocation;
            // com.rgbgame.game.Vector3d positionL = new com.rgbgame.game.Vector3d(goalLocation);
            Vector3d myVec = goalLocation.minus(myPlayer.particle[0].position);
            float normSqr = myVec.normSquared();
            if (normSqr > 3.0f) { // why are we doing this test again? it's
                // cheap and all, but...
                shootAngle += .01f;// random(-.02,.1);
                Vector3d radiusVec = new Vector3d(25 * cos(shootAngle),
                        25 * sin(shootAngle), 0);
                Vector3d diffVec = new Vector3d(-radiusVec.x / 10.0f,
                        -radiusVec.y / 10.0f - 2.0f, 0);// myVec.normalized().times(
                // 0
                // );//PApplet.min(3.0+10000.0/normSqr,20)
                // );
                int rnum = (int) (2.0f + 2 * sin(frameNumber / 100.0f
                        + shootAngle));
                int gnum = (int) (2.0f + 2 * sin(frameNumber / 100.0f + PI
                        * 2.0f / 3.0f + shootAngle));
                int bnum = (int) (2.0f + 2 * sin(frameNumber / 100.0f + PI
                        * 4.0f / 3.0f + shootAngle));
                burstFXParticles(rnum, position.plus(radiusVec), diffVec,
                        Color.red, .6f * PApplet.min(3.0f + 10000.0f / normSqr, 10.0f),
                        215.0f);
                burstFXParticles(gnum, position.plus(radiusVec), diffVec,
                        Color.green, .6f * PApplet.min(3.0f + 10000.0f / normSqr, 10.0f),
                        215.0f);
                burstFXParticles(bnum, position.plus(radiusVec), diffVec,
                        Color.blue, .6f * PApplet.min(3.0f + 10000.0f / normSqr, 10.0f),
                        215.0f);

                if (1.0f + 10000.0f / normSqr > 5.0f) {

                    for (int i = 0; i < myPlayer.particle.length; i++) {
                        // com.rgbgame.game.Vector3d myVec2 =
                        // goalLocation.minus(myPlayer.particle[i].position);
                        // myVec2.multiply(1000.0);
                        myPlayer.particle[i].position.x = goalLocation.x
                                * .001f + myPlayer.particle[i].position.x
                                * .999f; // Pull the player towards the goal
                        // for a satisfying end.
                        myPlayer.particle[i].position.y = goalLocation.y
                                * .001f + myPlayer.particle[i].position.y
                                * .999f;
                    }
                }
                if (1.0f + 10000.0f / normSqr > 20.0f) {
                    // player won
                    playerWonLevel();
                }
            } else {
                playerWonLevel(); // We probably shouldn't get here, because
                // you should have won already...but just in
                // case, here you go.
            }
        }
    }

    public void addFXParticle(Vector3d p, Vector3d plast, Color c, float a) {
        if (activeFXParticles >= numFXParticles) {
            return;
        }
        fxParticles[activeFXParticles].position.set(p);// .copy();
        fxParticles[activeFXParticles].positionLast.set(plast);// .copy();
        fxParticles[activeFXParticles].truePositionLast.set(plast);// .copy();
        fxParticles[activeFXParticles].myColor = c;// .copy();
        fxParticles[activeFXParticles].alphaValue = a;
        fxParticles[activeFXParticles].setAirDrag(-1.86f);
        activeFXParticles++;
        return;
    }

    public void removeFXParticle(int i) {
        // To remove, simply move the last one on the list over this one and
        // decrement the number of active ones
        if (activeFXParticles <= i) {
            return;
        }
        activeFXParticles--;
        fxParticles[i].position.set(fxParticles[activeFXParticles].position);
        fxParticles[i].positionLast
                .set(fxParticles[activeFXParticles].positionLast);
        fxParticles[i].truePositionLast
                .set(fxParticles[activeFXParticles].truePositionLast);
        fxParticles[i].myColor = fxParticles[activeFXParticles].myColor;
        fxParticles[i].alphaValue = fxParticles[activeFXParticles].alphaValue;
    }

    float fxAlphaDecay = 750.0f;

    Vector3d fxBuff = new Vector3d(0, 0, 0);

    public void stepFXParticles(float dt) {

        for (int i = 0; i < activeFXParticles; i++) {
            fxBuff.set(gravityForce);
            fxBuff.multiply(fxParticles[i].mass);
            fxParticles[i].forceAdd(fxBuff);
            fxParticles[i].timeStep(dt);
            fxParticles[i].alphaValue -= fxAlphaDecay * dt;
            if (fxParticles[i].alphaValue < 5)
                removeFXParticle(i);
        }
    }

    public void drawFXParticles() {
        // println("DRAWING FX PARTICLES");
        for (int i = 0; i < activeFXParticles; i++) {
            fxParticles[i].draw();
        }
        // println("DONE WITH FX PARTICLES");
    }

    public void burstFXParticles(int num, Vector3d sourceLoc,
                                 Vector3d direction, Color c, float scatter, float a) {
        for (int i = 0; i < num; i++) {
            // debugCounter++;
            Vector3d p = sourceLoc.copy();
            Vector3d randomVec = new Vector3d(10 * scatter, 0, 0);
            float scatterSqr = scatter * scatter;
            while (randomVec.normSquared() > scatterSqr) {
                randomVec.set(pApplet.random(-scatter, scatter), pApplet.random(-scatter,
                        scatter), 0);
            }
            p.add(randomVec);
            Vector3d plast = sourceLoc.minus(direction);
            Vector3d diff = p.minus(plast);
            p.add(diff);
            plast.add(diff);
            addFXParticle(p, plast, c, a);
        }
    }

    /*
      * void wallBox(float x1, float y1, float x2, float y2, ArrayList wallList){
      * wallList.add(new com.rgbgame.game.SolidWall2d(x1,y1,x1,y2)); wallList.add(new
      * com.rgbgame.game.SolidWall2d(x1,y2,x2,y2)); wallList.add(new com.rgbgame.game.SolidWall2d(x2,y2,x2,y1));
      * wallList.add(new com.rgbgame.game.SolidWall2d(x2,y1,x1,y1)); } void wall(float x1, float
      * y1, float x2, float y2, ArrayList wallList){ wallList.add(new
      * com.rgbgame.game.SolidWall2d(x1,y1,x2,y2)); }
      */

    // com.rgbgame.game.Spring, com.rgbgame.game.Particle, com.rgbgame.game.Strut, com.rgbgame.game.SpatialPartition, and com.rgbgame.game.Box2d classes

    public void setupSpatialPartition() {
        mySpatialPartition = new SpatialPartition(50, myWall, myStaticCStruts,
                myDynamicCStruts);
    }


    ArrayList globalParticleList = new ArrayList();
    int particleMax;

    public Particle getParticleByID(int _id) {
        if (_id > particleMax) {
            return null;
        }
        return (Particle) (globalParticleList.get(_id));
    }

    ArrayList globalStrutList = new ArrayList();

    public Strut getStrutByID(int _id) {
        return (Strut) (globalStrutList.get(_id));
    }

    public static final int NORMAL = 0;
    public static final int PUSH = 1;
    public static final int PULL = 2;

    public void generatePoses() {
        // debugCounter++;

        myParticle[0] = new Particle(
                new Vector3d(0.0f, 0.0f, 0.0f),
                myMass, myRadius, Color.gray);

        myParticle[1] = new Particle(
                new Vector3d(myLegLength, 0.0f, 0.0f),
                myColorMass, myColorRadius, Color.red);


        myParticle[2] = new Particle(new Vector3d
                (myLegLength * COS_TWOTHIRDS_PI, myLegLength * SIN_TWOTHIRDS_PI, 0.0f),
                myColorMass, myColorRadius, Color.green);


        myParticle[3] = new Particle(new Vector3d
                (myLegLength * COS_FOURTHIRDS_PI, myLegLength * SIN_FOURTHIRDS_PI, 0.0f),
                myColorMass, myColorRadius, Color.blue);


        myParticle[4] = new Particle(new Vector3d
                (myLegLength / 3, 0.0f, 0.0f),
                myMass, myRadius, Color.gray);


        myParticle[5] = new Particle(new Vector3d
                (myLegLength * COS_TWOTHIRDS_PI / 3, myLegLength * SIN_TWOTHIRDS_PI / 3, 0.0f),
                myMass, myRadius, Color.gray);


        myParticle[6] = new Particle(
                new Vector3d(myLegLength * COS_FOURTHIRDS_PI / 3, myLegLength * SIN_FOURTHIRDS_PI / 3, 0.0f),
                myMass, myRadius, Color.gray);


        myParticle[7] = new Particle(new Vector3d
                (myLegLength * 2.0f / 3, 0.0f, 0.0f),
                myMass, myRadius, Color.gray);


        myParticle[8] = new Particle(new Vector3d
                (myLegLength * COS_TWOTHIRDS_PI * 2.0f / 3, myLegLength * SIN_TWOTHIRDS_PI * 2.0f / 3, 0.0f),
                myMass, myRadius, Color.gray);


        myParticle[9] = new Particle(new Vector3d
                (myLegLength * COS_FOURTHIRDS_PI * 2.0f / 3, myLegLength * SIN_FOURTHIRDS_PI * 2.0f / 3, 0.0f),
                myMass, myRadius, Color.gray);

        for (int i = 0; i < 10; i++) {
            myParticle[i].alphaValue = 255.0f;
        }

        myPose = new Pose(myParticle);// Clear
    }

    public void resetPose(boolean isR, boolean isG, boolean isB) {
        //FIXME: this function is horrendously inefficient - since it's called
        //once a frame we should not be creating objects and calling so many
        //trig functions...

        // println("Reset at "+stepCount);
        // debugCounter++;
        float alphaFactor = curlSens * PApplet.abs(smoothedAngularVelocity)
                / (curlThresh + PApplet.abs(smoothedAngularVelocity) * curlSens);
        float angleRotation = -(2 * PI / 9.0f) * alphaFactor
                * smoothedAngularVelocity
                / (Math.abs(smoothedAngularVelocity) + .0001f);
        float cosRotate1 = COS_TWOTHIRDS_PI;
        float cosRotate2 = COS_FOURTHIRDS_PI;
        float sinRotate1 = SIN_TWOTHIRDS_PI;
        float sinRotate2 = SIN_FOURTHIRDS_PI;

        if (keyState[' ']) {
            cosRotate1 = cos(PI - PI/10);
            cosRotate2 = cos(PI + PI/10);
            sinRotate1 = sin(PI - PI/10);
            sinRotate2 = sin(PI + PI/10);
        }

        myParticle[0] = new Particle(new Vector3d(0.0f, 0.0f, 0.0f), myMass,
                myRadius, Color.gray);
        float defaultLegLength = myLegLength;


        if (isR) {
            myLegLength *= legExtendFactor;
        }
        myParticle[1] = new Particle(new Vector3d(myLegLength, 0.0f, 0.0f),
                myMass, myRadius, Color.red);
        myParticle[4] = new Particle(new Vector3d(myLegLength / 3, 0.0f, 0.0f),
                myMass, myRadius, Color.gray);
        myParticle[7] = new Particle(new Vector3d(myLegLength * 2.0f / 3, 0.0f,
                0.0f), myMass, myRadius, Color.gray);
        myLegLength = defaultLegLength;

        if (isG) {
            myLegLength *= legExtendFactor;
        }
        myParticle[2] = new Particle(new Vector3d(myLegLength
                * cosRotate1, myLegLength * sinRotate1,
                0.0f), myMass, myRadius, Color.green);
        myParticle[5] = new Particle(new Vector3d(myLegLength
                * cosRotate1 / 3, myLegLength
                * sinRotate1 / 3, 0.0f), myMass, myRadius,
                Color.gray);
        myParticle[8] = new Particle(new Vector3d(myLegLength
                * cosRotate1 * 2.0f / 3, myLegLength
                * sinRotate1 * 2.0f / 3, 0.0f), myMass, myRadius,
                Color.gray);
        myLegLength = defaultLegLength;


        if (isB) {
            myLegLength *= legExtendFactor;
        }
        myParticle[3] = new Particle(new Vector3d(myLegLength
                * cosRotate2, myLegLength * sinRotate2,
                0.0f), myMass, myRadius, Color.blue);
        myParticle[6] = new Particle(new Vector3d(myLegLength
                * cosRotate2 / 3, myLegLength
                * sinRotate2 / 3, 0.0f), myMass, myRadius,
                Color.gray);
        myParticle[9] = new Particle(new Vector3d(myLegLength
                * cosRotate2 * 2.0f / 3, myLegLength
                * sinRotate2 * 2.0f / 3, 0.0f), myMass, myRadius,
                Color.gray);
        myLegLength = defaultLegLength;

        if (invertedRGB) {
            angleRotation *= -1.0f;
        }

        Util.rotate2d(myParticle[4].position, angleRotation * 2);
        Util.rotate2d(myParticle[7].position, angleRotation);

        Util.rotate2d(myParticle[5].position, angleRotation * 2);
        Util.rotate2d(myParticle[8].position, angleRotation);

        Util.rotate2d(myParticle[6].position, angleRotation * 2);
        Util.rotate2d(myParticle[9].position, angleRotation);
        /*
           * float norm1 = myParticle[1].position.norm(); float norm2 =
           * myParticle[2].position.norm(); float norm3 =
           * myParticle[3].position.norm(); myParticle[4].position.multiply(
           * ((norm1*.6666 + norm2*.3333)*alphaFactor +
           * myParticle[4].position.norm()*(1.0-alphaFactor))/myParticle[4].position.norm() );
           * myParticle[7].position.multiply( ((norm1*.3333 +
           * norm2*.6666)*alphaFactor +
           * myParticle[7].position.norm()*(1.0-alphaFactor))/myParticle[7].position.norm() );
           * myParticle[5].position.multiply( ((norm2*.6666 +
           * norm3*.3333)*alphaFactor +
           * myParticle[5].position.norm()*(1.0-alphaFactor))/myParticle[5].position.norm() );
           * myParticle[8].position.multiply( ((norm2*.3333 +
           * norm3*.6666)*alphaFactor +
           * myParticle[8].position.norm()*(1.0-alphaFactor))/myParticle[8].position.norm() );
           * myParticle[6].position.multiply( ((norm3*.6666 +
           * norm1*.3333)*alphaFactor +
           * myParticle[6].position.norm()*(1.0-alphaFactor))/myParticle[6].position.norm() );
           * myParticle[9].position.multiply( ((norm3*.3333 +
           * norm1*.6666)*alphaFactor +
           * myParticle[9].position.norm()*(1.0-alphaFactor))/myParticle[9].position.norm() );
           */

        myPose = new Pose(myParticle);
        myPlayer.setPose(myPose);
    }


    /**
     * Math, Drawing, and other Utility functions/classes that don't have a
     * better home
     */
    public void drawStars() {
        //if (true) return;
        int res = 50;
        pApplet.stroke(100);
        pApplet.noFill();
        pApplet.fill(100);

        int xoff = (int) -cameraX / 4 % res;
        int yoff = (int) -cameraY / 4 % res;
        float jval = -cameraY + 1.2f * height + 2 + res;
        float kval = -cameraX + 1.2f * width + 2 + res;

        // OPENGL drawing - should be quicker

        if (graphicsMode.equals(PApplet.OPENGL)) {
            res = 50;
            xoff = (int) -cameraX / 4 % res;
            yoff = (int) -cameraY / 4 % res;
            jval = 1.2f * height + 2 + res;
            kval = 1.2f * width + 2 + res;

            gl.glPointSize(3);
            // gl.glEnable(GL.GL_POINT_SMOOTH);
            gl.glBegin(GL.GL_POINTS);
            gl.glColor3f(75 / 255.0f, 75 / 255.0f, 75 / 255.0f);
            for (float j = -height; j < jval; j += res) {
                for (float k = -width; k < kval; k += res) {
                    // gl.glColor4f(100, 100, 100, 255);
                    //TODO: change -415.6922f to represent calculated value in case
                    //we change size of window
                    gl.glVertex3f(k - xoff, j - yoff, -415.6922f);
                }
            }
            res = 75;
            xoff = (int) (-cameraX / 2.666f) % res;
            yoff = (int) (-cameraY / 2.666f) % res;
            jval = 1.2f * height + 2 + res;
            kval = 1.2f * width + 2 + res;
            // gl.glPointSize(2);
            gl.glColor3f(125 / 255.0f, 125 / 255.0f, 125 / 255.0f);
            for (float j = -height; j < jval; j += res) {
                for (float k = -width; k < kval; k += res) {
                    // gl.glColor4f(150, 150, 150, 255);
                    gl.glVertex3f(k - xoff, j - yoff, -415.6922f);
                }
            }
            res = 100;
            xoff = (int) (-cameraX / 2) % res;
            yoff = (int) (-cameraY / 2) % res;
            jval = 1.2f * height + 2 + res;
            kval = 1.2f * width + 2 + res;
            // gl.glPointSize(3);
            gl.glColor3f(200 / 255.0f, 200 / 255.0f, 200 / 255.0f);
            for (float j = -height; j < jval; j += res) {
                for (float k = -width; k < kval; k += res) {
                    // gl.glColor4f(255, 255, 255, 255);
                    gl.glVertex3f(k - xoff, j - yoff, -415.6922f);
                }
            }
            gl.glEnd();
        } else {
            // NON-OPENGL

            for (float j = -cameraY - height; j < jval; j += res) {
                for (float k = -cameraX - width; k < kval; k += res) {
                    pApplet.point(k - xoff, j - yoff);//
                    // pApplet.point(pointTransform(k-xoff,
                    // j-yoff).x,pointTransform(k-xoff, j-yoff).y);
                }
            }

            res = 75;
            pApplet.stroke(150);
            xoff = (int) (-cameraX / 2.666f) % res;
            yoff = (int) (-cameraY / 2.666f) % res;
            jval = -cameraY + 1.2f * height + 2 + res;
            kval = -cameraX + 1.2f * width + 2 + res;
            for (float j = -cameraY - height; j < jval; j += res) {
                for (float k = -cameraX - width; k < kval; k += res) {
                    // ellipse(k-xoff,j-yoff,1.1,1.1);//
                    pApplet.point(k - xoff, j - yoff);//
                    // else line(k-xoff,j-yoff,k-xoff+1,j-yoff+1);
                    // pApplet.point(pointTransform(k-xoff,
                    // j-yoff).x,pointTransform(k-xoff, j-yoff).y);
                }
            }
            res = 100;
            pApplet.stroke(255);
            xoff = (int) -cameraX / 2 % res;
            yoff = (int) -cameraY / 2 % res;
            jval = -cameraY + 1.2f * height + 2 + res;
            kval = -cameraX + 1.2f * width + 2 + res;
            for (float j = -cameraY - height; j < jval; j += res) {
                for (float k = -cameraX - width; k < kval; k += res) {
                    // ellipse(k-xoff,j-yoff,1,1);//
                    pApplet.point(k - xoff, j - yoff);
                    // pApplet.point(pointTransform(k-xoff,
                    // j-yoff).x,pointTransform(k-xoff, j-yoff).y);
                    // else line(k-xoff,j-yoff,k-xoff+1,j-yoff+1);
                }
            }
        }

        pApplet.noStroke();
    }

    public void handleCamera() {
        if (!myPlayer.simulating) return;
        // deal with camera
        float camDistX = -myPlayer.getParticle(0).position.x + width / 2
                - cameraX; // how far behind the camera is
        float camDistY = -myPlayer.getParticle(0).position.y + height / 2
                - cameraY;
        // println(camDistX);
        // println(cameraX);
        // println(myPlayer.getParticle(0).position.x);

        if (PApplet.abs(camDistX) > width / 3.0f) {
            cameraX += camDistX
                    - ((camDistX > 0) ? width / 3.0f : -width / 3.0f);
        }
        if (PApplet.abs(camDistY) > height / 3.0f) {
            cameraY += camDistY
                    - ((camDistY > 0) ? height / 3.0f : -height / 3.0f);
        }

        // else {myXMultiplier *= 1.0/1.005; if (myXMultiplier < 1.0)
        // {myXMultiplier = 1.0;}}
        // if ( Math.abs(camDistY) > height/3.0 ){ myYMultiplier *= 1.005; }
        // else {myYMultiplier *= 1.0/1.005; if (myYMultiplier < 1.0)
        // {myYMultiplier = 1.0;}}

        cameraX = -camFollowSpeed * myXMultiplier
                * (myPlayer.getParticle(0).position.x - width / 2)
                + (1.0f - camFollowSpeed * myXMultiplier) * cameraX;
        cameraY = -camFollowSpeed * myYMultiplier
                * (myPlayer.getParticle(0).position.y - height / 2)
                + (1.0f - camFollowSpeed * myYMultiplier) * cameraY;
        cameraZ = 0;
    }

    public void calcAngularVelocity() {
        // debugCounter++;
        Particle p0 = myPlayer.getParticle(0);
        Particle p1 = myPlayer.getParticle(1);
        Particle p2 = myPlayer.getParticle(2);
        Particle p3 = myPlayer.getParticle(3);
        p0.setVel();
        p1.setVel();
        p2.setVel();
        p3.setVel();
        Vector3d rad = p1.position.minus(p0.position);
        Vector3d vel = p1.velocity.minus(p0.velocity);
        float angVelocity = rad.cross(vel).z / rad.norm();
        rad = p2.position.minus(p0.position);
        vel = p2.velocity.minus(p0.velocity);
        angVelocity += rad.cross(vel).z / rad.norm();
        rad = p3.position.minus(p0.position);
        vel = p3.velocity.minus(p0.velocity);
        angVelocity += rad.cross(vel).z / rad.norm();
        angVelocity *= 1.0f / 3.0f;
        angularVelocity = angVelocity;
        smoothedAngularVelocity *= .9f;
        smoothedAngularVelocity += (angularVelocity * .1f);

        // DEBUGME - need to set this so we can choose the right parameters for
        // each level
        myPlayer.setSpringStrength(myPlayer.springConstant
                + (MAX_SPRING_CONSTANT - myPlayer.springConstant)
                * PApplet.abs(PApplet.min(1.0f, smoothedAngularVelocity / 10000.0f)));
        // println(smoothedAngularVelocity);
    }


    // com.rgbgame.game.Particle tempParticle; //use for better grabbing
    public void handleStrutAndParticle(Particle strutA, Particle strutB,
                                       Particle testParticle, Strut myStrt) {
        if (Util.collideTest(testParticle, strutA.position, strutB.position)) {
            if (testParticle.myColor.getRed() > 200) {
                redDStruts.add(new Strut(this, testParticle, strutA, REDINDEX));
                redDStruts.add(new Strut(this, testParticle, strutB, REDINDEX));
                if (rnodrawStrut == null) {
                    myStrt.setDraw(false);
                    //myStrt.setApply(false);
                    rnodrawStrut = myStrt;
                }
            } else if (testParticle.myColor.getGreen() > 200) {
                greenDStruts.add(new Strut(this, testParticle, strutA, GREENINDEX));
                greenDStruts.add(new Strut(this, testParticle, strutB, GREENINDEX));
                if (gnodrawStrut == null) {
                    myStrt.setDraw(false);
                    //myStrt.setApply(false);
                    gnodrawStrut = myStrt;
                }
            } else if (testParticle.myColor.getBlue() > 200) {
                blueDStruts.add(new Strut(this, testParticle, strutA, BLUEINDEX));
                blueDStruts.add(new Strut(this, testParticle, strutB, BLUEINDEX));
                if (bnodrawStrut == null) {
                    myStrt.setDraw(false);
                    //myStrt.setApply(false);
                    bnodrawStrut = myStrt;
                }
            }
        }
    }


    // Useful drawing functions
    public final void circle(float x, float y, float radius) {
        pApplet.ellipse(x, y, 2 * radius, 2 * radius);
    }

    public final void circle(Vector3d p, float r) {
        pApplet.ellipse(p.x, p.y, 2 * r, 2 * r);
    }

    public final void fakeSphere(float x, float y, float z, float r) {
        // Quick and dirty "sphere" made up of two circles
        if (sphere3d) {
            pApplet.pushMatrix();
            pApplet.translate(x, y, z);
            pApplet.ellipse(0, 0, 2 * r, 2 * r);
            /*
                * pushMatrix(); rotateY(3.14/2.0); ellipse(0,0,2*r,2*r);
                * popMatrix();
                */
            pApplet.popMatrix();
        } else {
            pApplet.ellipse(x, y, 2 * r, 2 * r);
        }
    }

    public final void fakeSphere(Vector3d pos, float r) {
        fakeSphere(pos.x, pos.y, pos.z, r);
    }

    static public void main(String args[]) {
        Launcher l = new Launcher();
        Launcher.current = Launcher.GAME;
        Launcher.main(args);
    }
}
